package cn.edu.cug.cs.gtl.gsm.mongo;

import cn.edu.cug.cs.gtl.common.Identifier;
import cn.edu.cug.cs.gtl.gsm.PaginationStorageManager;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientURI;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.MongoIterable;
import com.mongodb.client.model.Filters;
import org.bson.Document;
import org.bson.types.Binary;

import java.io.IOException;
import java.io.RandomAccessFile;

public class MongoStorageManager extends PaginationStorageManager {
    MongoClient mongoClient;
    MongoDatabase mongoDatabase;

    public static final String NAME="name";
    public static final String META="meta";
    public static final String INDEX="index";
    public static final String CONTENT="content";
    public static final String IDENTIFIER="identifier";
    public static final String DATA="data";

    /**
     * @param mongoClientURI mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database.collection][?options]]
     * @param collectionSize
     * @param pageSize
     */
    public MongoStorageManager(String mongoClientURI, String baseName, long collectionSize, int pageSize,boolean overwrite) {
        this(new MongoClientURI(mongoClientURI),baseName,collectionSize, pageSize,overwrite);
    }

    /**
     * @param collectionSize
     * @param pageSize
     */
    public MongoStorageManager(MongoClientURI mongoClientURI, String baseName, long collectionSize, int pageSize,boolean overwrite) {
        super(baseName,collectionSize, pageSize);
        this.mongoClient = new MongoClient(mongoClientURI);
        boolean exist = false;
        MongoIterable<String> si = this.mongoClient.listDatabaseNames();
        MongoCursor<String> cur = si.iterator();
        while(cur.hasNext()){
            if(getBaseName().equals(cur.next())){
                exist=true;
                break;
            }
        }

        if(overwrite){
            if(exist)
                this.mongoClient.dropDatabase(getBaseName());
            this.mongoDatabase = this.mongoClient.getDatabase(baseName);
        }
        else{
            this.mongoDatabase = this.mongoClient.getDatabase(baseName);
            if(exist){
                try{
                    readMetaFromStorage();
                    readIndicesFromStorage();
                }
                catch (IOException e){
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    protected void readBufferFromPage(Identifier pageIdentifier) throws IOException {
        int order = this.metaItem.getCollectionOrder(pageIdentifier);
        String dataCollectionName = this.metaItem.getBaseName() + "_"+String.valueOf(order).toString();
        //需要进行集合切换
        if(order!=getCurrentCollectionOrder()){
            setCurrentCollectionOrder(order);
        }
        MongoCollection<Document> c = this.mongoDatabase.getCollection(dataCollectionName);
        String sid = pageIdentifier.toString();
        MongoCursor<Document> cur = c.find(Filters.eq(IDENTIFIER,sid)).cursor();
        if(cur.hasNext()){
            Document d = cur.next();
            Object o = d.get(DATA);
            Binary b = (Binary) o;
            byte [] bs = b.getData();
            System.arraycopy(bs,0,this.pageBuffer,0,Math.min(bs.length,getPageSize()));
        }
        cur.close();
    }

    @Override
    protected void writeBufferToPage(Identifier pageIdentifier) throws IOException {
        int order = this.metaItem.getCollectionOrder(pageIdentifier);
        String dataCollectionName = this.metaItem.getBaseName() + "_"+String.valueOf(order).toString();
        //需要进行集合切换
        if(order!=getCurrentCollectionOrder()){
            setCurrentCollectionOrder(order);
        }
        MongoCollection<Document> c = this.mongoDatabase.getCollection(dataCollectionName);
        String sid = pageIdentifier.toString();
        Document d = new Document()
                .append(IDENTIFIER,sid)
                .append(DATA,this.pageBuffer);
        MongoCursor<Document> cur = c.find(Filters.eq(IDENTIFIER,sid)).cursor();
        if(cur.hasNext()){
            c.updateOne(Filters.eq(IDENTIFIER,sid),d);
        }
        else{
            c.insertOne(d);
        }
    }

    @Override
    protected void readMetaFromStorage() throws IOException {
        MongoCollection<Document> s = this.mongoDatabase.getCollection(getBaseName());
        MongoCursor<Document> c =  s.find(Filters.eq(NAME,META)).cursor();
        if(c.hasNext()){
            Object o = c.next().get(CONTENT);
            Binary b = (Binary) o;
            byte [] bs = b.getData();
            this.metaItem.loadFromByteArray(bs);
        }
    }

    @Override
    protected void writeMetaToStorage() throws IOException {
        MongoCollection<Document> c = this.mongoDatabase.getCollection(getBaseName());
        byte[] bs = this.metaItem.storeToByteArray();

        Document d = new Document()
                .append(NAME,META)
                .append(CONTENT,bs);

        if(c.find(Filters.eq(NAME,META)).cursor().hasNext()){
            c.updateOne(Filters.eq(NAME,META),d);
        }
        else{
            c.insertOne(d);
        }

    }

    @Override
    protected void readIndicesFromStorage() throws IOException {
        MongoCollection<Document> s = this.mongoDatabase.getCollection(getBaseName());
        MongoCursor<Document> c =  s.find(Filters.eq(NAME,INDEX)).cursor();
        if(c.hasNext()){
            Object o = c.next().get(CONTENT);
            Binary b = (Binary) o;
            byte [] bs = b.getData();
            this.pageMap.loadFromByteArray(bs);
        }
    }

    @Override
    protected void writeIndicesToStorage() throws IOException {
        MongoCollection<Document> c = this.mongoDatabase.getCollection(getBaseName());

        byte[] bs = this.pageMap.storeToByteArray();
        Document d = new Document()
                .append(NAME,INDEX)
                .append(CONTENT,bs);

        if(c.find(Filters.eq(NAME,INDEX)).cursor().hasNext()){
            c.updateOne(Filters.eq(NAME,INDEX),d);
        }
        else{
            c.insertOne(d);
        }
    }

    @Override
    protected void closeStorages() throws IOException {
        this.mongoClient.close();
    }
}
